package dao.impl;

import dao.GenericDAO;
import entity.*;
import java.io.Serializable;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.hibernate.transform.DistinctRootEntityResultTransformer;
import util.HibernateUtil;

/**
 * Реализация интерфейса GenericDAO Свзять с базой данных черех Hibernate
 */
public class GenericDAOImpl<T, K extends Serializable> implements GenericDAO<T, K> {

    private Class<T> type;

    /**
     * Конструктор
     *
     * @param type
     * @throws Exception
     */
    public GenericDAOImpl(Class<T> type) throws Exception {
        this.type = type;
    }

    /**
     * Добавление нового объекта
     *
     * @param entity
     * @throws Exception
     */
    @Override
    public void add(T entity) throws Exception {
        Session session = createSession();
        session.save(entity);
        closeSession(session);
    }

    /**
     * Удаление объекта
     *
     * @param entity
     * @throws Exception
     */
    @Override
    public void delete(T entity) throws Exception {
        Session session = createSession();
        session.delete(entity);

        closeSession(session);

    }

    /**
     * Измненеие объекта
     */
    @Override
    public void update(T entity) throws Exception {
        Session session = createSession();
        session.saveOrUpdate(entity);
        closeSession(session);
    }

    /**
     * Получение объекта по идентификатору
     *
     * @param id
     * @return
     * @throws Exception
     */
    @Override
    public T getById(K id) throws Exception {
        Session session = createSession();
        T entity = (T) session.get(type, id);
        closeSession(session);
        return entity;
    }

    /**
     * Получение коллексии всех объектов
     *
     * @return Collection<T>
     * @throws Exception
     */
    @Override
    public Collection<T> getAll() throws Exception {
        Session session = createSession();

        /*
         * DistinctRootEntityResultTransformer - для того что бы избежать
         * дубликатов в выводе, если таблица имеет manyToMany или OneToMany
         * вместо можно выставить свзять lazy
         */
        Criteria cr = session.createCriteria(type).setResultTransformer(new DistinctRootEntityResultTransformer());
        List<T> retList = (List<T>) cr.list();
        closeSession(session);
        return retList;
    }

    /**
     * Получение объекта по заданому телефону
     *
     * @param phone
     * @return
     * @throws Exception
     */
    @Override
    public T getByPhone(Phone phone) throws Exception {
        Session session = createSession();
        String nnum = normalizeNumber(phone.getNumber());
        Criteria cr = session.createCriteria(Phone.class).add(Restrictions.eq("number", nnum));
        List<Phone> phoneList = cr.list();
        closeSession(session);
        if (phoneList != null && !phoneList.isEmpty()) {
            OrgUnitPost oup = phoneList.get(0).getPost();
            if (type == OrgUnitPost.class) {
                return (T) oup;
            }

            if (type == Person.class) {
                return (T) oup.getPerson();
            }

            if (type == Room.class) {
                return (T) oup.getRoom();
            }

            if (type == Phone.class) {
                return (T) phoneList.get(0);
            }
        } else {
            return null;
        }
        return null;
    }

    /**
     * Получение объекта PhoneType по имени
     *
     * @param name
     * @return
     * @throws Exception
     */
    @Override
    public PhoneType getByPhoneTypeByName(String name) throws Exception {
        Session session = createSession();
        Criteria cr = session.createCriteria(PhoneType.class).add(Restrictions.eq("name", name));
        List<PhoneType> phoneType = (List<PhoneType>) cr.list();
        closeSession(session);

        if (phoneType == null || phoneType.isEmpty()) {
            return null;
        } else {
            return phoneType.get(0);
        }
    }

    /**
     * Получение коллекции объектов по Room
     *
     * @param room
     * @return
     * @throws Exception
     */
    @Override
    public Collection<T> getByRoom(Room room) throws Exception {
        if (type == Room.class) {
            Session session = createSession();
            Criteria cr = session.createCriteria(type).add(Restrictions.eq("name", room.getName()));
            List<Room> r = (List<Room>) cr.list();
            closeSession(session);

            return (Collection<T>) r;
        }

        if (type == OrgUnitPost.class) {
            return null;
        }
        return null;
    }

    /**
     * Получение родителя OrgUnit (Можно просто через orgUnit.getPerent(), без
     * запроса в БД)
     *
     * @param orgUnit
     * @return
     */
    @Override
    public OrgUnit getParent(OrgUnit orgUnit) {
        return orgUnit.getParentUnit();
    }

    /**
     * Получение коллекции объектов по OrgUnit
     *
     * @param orgUnit
     * @return
     * @throws Exception
     */
    @Override
    public Collection<T> getByOrgUnit(OrgUnit orgUnit) throws Exception {
        Session session = createSession();
        if (type == OrgUnitPost.class) {
            Criteria cr = session.createCriteria(OrgUnitPost.class).add(Restrictions.eq("orgUnit", orgUnit));
            List<OrgUnitPost> oupList = cr.list();
            closeSession(session);
            return (List<T>) oupList;
        }
        if (type == OrgUnit.class) {
            Criteria cr = session.createCriteria(OrgUnit.class).add(Restrictions.eq("name", orgUnit.getName()));
            List<OrgUnit> oupList = cr.list();
            closeSession(session);
            return (List<T>) oupList;
        }
        throw new UnsupportedOperationException("Works only  for T == OrgUnitPost.");

    }

    /**
     * Получение коллекции объектов по Person
     *
     * @param person
     * @return
     * @throws Exception
     */
    @Override
    public Collection<T> getByPerson(Person person) throws Exception {
        Session session = createSession();
        Criteria cr = session.createCriteria(Person.class);
        if (person.getName() != null) {
            cr.add(Restrictions.like("name", person.getName()));
        }
        if (person.getSurname() != null) {
            cr.add(Restrictions.like("surname", person.getSurname()));
        }
        if (person.getFathername() != null) {
            cr.add(Restrictions.like("fathername", person.getFathername()));
        }

        List<Person> personList = cr.list();

        closeSession(session);
        if (type == Person.class) {
            return (ArrayList<T>) personList;
        }

        if (type == OrgUnitPost.class) {
            //Не уверен что можно ArrayList
            ArrayList<OrgUnitPost> oupList = new ArrayList<OrgUnitPost>();
            for (Person p : personList) {
                oupList.addAll(p.getPosts());
            }
            return (ArrayList<T>) oupList;
        }
        throw new UnsupportedOperationException("Works only for T == Person || T == OrgUnitPost.");
    }

    /**
     * получение поллекции объектов по OrgUnitType
     *
     * @param out
     * @return
     * @throws Exception
     */
    @Override
    public Collection<OrgUnit> getByOrgUnitType(OrgUnitType out) throws Exception {
        OrgUnitType outS;
        Session session = createSession();
        if (out.getId() != null) {
            outS = out;
        } else {
            outS = (OrgUnitType) session.createCriteria(OrgUnitType.class).add(Restrictions.eq("name", out.getName())).uniqueResult();
        }

        List<OrgUnit> ouList = session.createCriteria(OrgUnit.class).add(Restrictions.eq("orgUnitType.id", outS.getId())).list();
        closeSession(session);
        return ouList;
    }

    /**
     * Открытие сессии
     *
     * @return Сессия
     */
    private Session createSession() {
        Session session = null;

        session = HibernateUtil.getSession();
        session.beginTransaction();

        return session;
    }

    /**
     * Закрытие сессии
     *
     * @param session
     * @throws Exception
     */
    private void closeSession(Session session) throws Exception {
        Exception ex = null;
        try {
            session.getTransaction().commit();
        } catch (Exception e) {
            ex = e;
        } finally {
            if (session != null && session.isOpen()) {
                System.out.println("close in fin");
                session.flush();
                session.close();
                if (ex != null) {
                    throw ex;
                }
            }
        }
    }

    /*
     * Удаляет из строки все не численые символы
     */
    private String normalizeNumber(String number) {
        String n_number;
        n_number = number.replaceAll("[^\\d]", "");
        return n_number;
    }

    @Override
    public List<Post> getByPost(Post postEntity) throws Exception {
        Session session = createSession();
        Criteria cr = session.createCriteria(Post.class).add(Restrictions.eq("name", postEntity.getName()));
        List<Post> postList = (List<Post>) cr.list();
        closeSession(session);
        if (postList == null || postList.isEmpty()) {
            return null;
        } else {
            return postList;
        }
    }

    /**
     * Поиск имени
     *
     * @param name
     * @return OrgUnitType
     * @throws Exception
     */
    @Override
    public Collection<OrgUnitPost> searchByName(String name) throws Exception {
        System.out.println("Search by name:" + name);
        Session session = createSession();
        //OrgUnitType only
        Collection<OrgUnitPost> postList = session.createQuery("FROM OrgUnitPost WHERE person.surname LIKE '%" + name + "%'").list();
        return postList;
    }

    /**
     * Поиск по телефону
     *
     * @param phone
     * @return
     * @throws Exception
     */
    @Override
    public Collection<OrgUnitPost> searchByPhone(String phone) throws Exception {
        System.out.println("Search by phone:" + phone);
        Session session = createSession();
        ArrayList<OrgUnitPost> postList = new ArrayList<OrgUnitPost>();
        //OrgUnitType only
        Collection<Phone> phones = session.createQuery("FROM Phone WHERE number LIKE '%" + phone + "%'").list();
        //Вдруг не одна запись с телефоном, надёжней так
        for (Phone p : phones) {
            postList.add(p.getPost());
        }
        return postList;
    }

    @Override
    public Collection<OrgUnitPost> searchByRoom(String room) throws Exception {
        System.out.println("Search by room:" + room);
        Session session = createSession();
        //OrgUnitType only
        Collection<OrgUnitPost> postList = session.createQuery("FROM OrgUnitPost WHERE room.name LIKE '%" + room + "%'").list();
        return postList;
    }

    @Override
    public OrgUnitPost getOrgUnitPostByOrgUnitPost(OrgUnitPost oup) throws Exception {
        Session session = createSession();

        Criteria cr = session.createCriteria(OrgUnitPost.class);
        cr.add(Restrictions.eq("post", oup.getPost()));
        cr.add(Restrictions.eq("person", oup.getPerson()));
        cr.add(Restrictions.eq("room", oup.getRoom()));
        cr.add(Restrictions.eq("orgUnit", oup.getOrgUnit()));

        OrgUnitPost answer = null;
        List res = cr.list();
        if (res.size() > 0) {
            answer = (OrgUnitPost) res.get(0);
        }

        return answer;

    }

    @Override
    public User getUserByName(String name) throws Exception {        
        Session session = createSession();
        Criteria cr = session.createCriteria(User.class);
        cr.add(Restrictions.eq("name", name));
        List res = cr.list();
        User answer = null;
        if (res.size() > 0) {
            answer = (User) res.get(0);
        }
        return answer;
    }

    @Override
    public User getUserById(Long id) throws Exception {
         Session session = createSession();
        Criteria cr = session.createCriteria(User.class);
        cr.add(Restrictions.eq("id", id));
        List res = cr.list();
        User answer = null;
        if (res.size() > 0) {
            answer = (User) res.get(0);
        }
        return answer;
    }
    
     /**
     * Получение дайджеста строки.
     *
     * @param password
     * @return
     * @throws Exception
     */
    public String getPasswordDigest(String password) throws Exception {
        String pwd;
        byte[] pwdByteArray;
        MessageDigest md = MessageDigest.getInstance("SHA");
        md.update(password.getBytes());
        pwdByteArray = md.digest();
        pwd = "";
        for (int i = 0; i < pwdByteArray.length; i++) {
            pwd += (new Byte(pwdByteArray[i])).intValue();
        }
        return pwd;
    }

    @Override
    public void addUser(User user) throws Exception {
        user.setDigest(getPasswordDigest(user.getDigest()));
        add((T)user);
    }
}
